/*
 * Copyright 2012 JooKar BV (info@jookar.nl)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.jookar.android.utils.gesture;

import java.util.Vector;

import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;

/**
 * The SwipeDetector is a utility class to implement Swipe recognition.<br/>
 * The easiest way is to simply create an instance of the {@link OnSwipeAdapter} class and pass
 * the {@link View} that has to react to swipes. On the adapter the appropriate events will be
 * triggered in case of a swipe. Simply override these event methods to implement you own
 * behaviour.
 * 
 * If you want more control, implement the {@link OnSwipeListener} interface.
 * @author Joost A. Bloemsma
 *
 */
public class SwipeDetector extends SimpleOnGestureListener {
	private static final String DEBUG_TAG = SwipeDetector.class.getName();
	
	private int _minDistance = 120;
    private int _maxOffPath = 250;
    private int _minSpeed = 200;
    private Vector<OnSwipeListener> listeners = new Vector<SwipeDetector.OnSwipeListener>();
    private GestureDetector _gDetector;
    
    /**
     * Set the minimal distance for the swipe 
     * @param minDistance
     */
    public void setMinDistance(int minDistance) {
    	_minDistance = minDistance;
    }
    
    /**
     * The maximum deviation of the movement
     * @param maxOffPath
     */
    public void setMaxOffPath(int maxOffPath) {
    	_maxOffPath = maxOffPath;
    }
    
    /**
     * The minimal speed of the swipe in pixels per second
     * @param minSpeed
     */
    public void setMinSpeed(int minSpeed) {
    	_minSpeed = minSpeed;
    }
    
	@Override
    public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		float velocity = Math.max(Math.abs(velocityX), Math.abs(velocityY));
		
		// A swipe must have a minimal speed.
		if (velocity < _minSpeed) return false;
		
		float distY = Math.abs(e1.getY() - e2.getY());
		float distX = Math.abs(e1.getX() - e2.getX());
		
		if ((distX > _maxOffPath) && !(distY > _maxOffPath)) {
			// Horizontal swipe, now test left or right
			if (e1.getX() - e2.getX() > _minDistance) {
				// Left
				Log.d(DEBUG_TAG, "Firing onLeftSwipe");
				return fireOnLeftSwipe(distX, velocity);
			}
			
			if (e2.getX() - e1.getX() > _minDistance) {
				// Right
				Log.d(DEBUG_TAG, "Firing onRightSwipe");
				return fireOnRightSwipe(distX, velocity);
			}
			
		}
		
		if ((distY > _maxOffPath) && !(distX > _maxOffPath)) {
			// Vertical swipe, now test up or down
			if (e1.getY() - e2.getY() > _minDistance) {
				// Up
				Log.d(DEBUG_TAG, "Firing onUpSwipe");
				return fireOnUpSwipe(distY, velocity);
			}
			
			if (e2.getY() - e1.getY() > _minDistance) {
				// Down
				Log.d(DEBUG_TAG, "Firing onDownSwipe");
				return fireOnDownSwipe(distY, velocity);
			}
		}

		// Obviously not a valid swipe
        return false;
    }
	
	/**
	 * Returns a valid {@link GestureDetector} initialised with this instance of the {@link SwipeDetector} as
	 * the {@link OnGestureListener} implementation.
	 * @return a {@link GestureDetector}
	 */
	public GestureDetector getGestureDetector() {
		if (_gDetector == null) {
			_gDetector = new GestureDetector(this);
		}
		return _gDetector;
	}
	/**
	 * Add an {@link OnSwipeListener}
	 * @param listener
	 */
	public void addListener (OnSwipeListener listener) {
		this.listeners.add(listener);
	}
	
	/**
	 * Remove an {@link OnSwipeListener}
	 * @param listener
	 * @return True if the listener was found. False otherwise 
	 */
	public boolean removeListener(OnSwipeListener listener) {
		return this.listeners.remove(listener);
	}
	
	/* Event delegators */
	private boolean fireOnUpSwipe(float distance, float velocity) {
		for (OnSwipeListener listener : listeners) {
			if (listener != null) {
				if (listener.onUpSwipe(distance, velocity)) return true;
			}
		}
		return false;
	}
	
	private boolean fireOnDownSwipe(float distance, float velocity) {
		for (OnSwipeListener listener : listeners) {
			if (listener != null) {
				if (listener.onDownSwipe(distance, velocity)) return true;
			}
		}
		return false;
	}
	
	private boolean fireOnLeftSwipe(float distance, float velocity) {
		for (OnSwipeListener listener : listeners) {
			if (listener != null) {
				if (listener.onLeftSwipe(distance, velocity)) return true;
			}
		}
		return false;
	}
	
	private boolean fireOnRightSwipe(float distance, float velocity) {
		for (OnSwipeListener listener : listeners) {
			if (listener != null) {
				if (listener.onRightSwipe(distance, velocity)) return true;
			}
		}
		return false;
	}
	
    /**
	 * Defines the events the {@link SwipeDetector} can trigger.<br/>
	 * For easy implementation extend the {@link OnSwipeAdapter} class. 
	 * @author Joost A. Bloemsma
	 *
	 */
	public static interface OnSwipeListener extends OnClickListener, OnTouchListener {
		
		/**
		 * Is triggered when a swipe gesture occurs in the up direction
		 * @param distance The distance in pixels of the swipe
		 * @param velocity The velocity of the swipe
		 * @return Return 'true'if the event is consumed. 
		 */
		public boolean onUpSwipe(float distance, float velocity);
		
		/**
		 * Is triggered when a swipe gesture occurs in the down direction
		 * @param distance The distance in pixels of the swipe
		 * @param velocity The velocity of the swipe
		 * @return Return 'true'if the event is consumed. 
		 */
		public boolean onDownSwipe(float distance, float velocity);
		
		/**
		 * Is triggered when a swipe gesture occurs in the left direction
		 * @param distance The distance in pixels of the swipe
		 * @param velocity The velocity of the swipe
		 * @return Return 'true'if the event is consumed. 
		 */
		public boolean onLeftSwipe(float distance, float velocity);
		
		/**
		 * Is triggered when a swipe gesture occurs in the right direction
		 * @param distance The distance in pixels of the swipe
		 * @param velocity The velocity of the swipe
		 * @return Return 'true'if the event is consumed. 
		 */
		public boolean onRightSwipe(float distance, float velocity);
	}
	 
	 /**
	  * Basic {@link OnSwipeListener} implementation.<br/>
	  * Returns false for all events. Override when needed.
	 * @author Joost A. Bloemsma
	 *
	 */
	public static class OnSwipeAdapter implements OnSwipeListener {
		private View _target;
		private SwipeDetector _detector;
		private GestureDetector _gDetector;
		
		/**
		 * Constructs a new OnSwipeAdapter instance
		 * @param target
		 */
		public OnSwipeAdapter(View target) {
			super();
			_target = target;
			_detector = new SwipeDetector();
			_gDetector = _detector.getGestureDetector();
			_detector.addListener(this);
			_target.setOnTouchListener(this);
			_target.setOnClickListener(this);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean onUpSwipe(float distance, float velocity) {
			return false;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean onDownSwipe(float distance, float velocity) {
			return false;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean onLeftSwipe(float distance, float velocity) {
			return false;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean onRightSwipe(float distance, float velocity) {
			return false;
		}


		/**
		 * {@inheritDoc}
		 */
		@Override
		public final boolean onTouch(View v, MotionEvent e) {
			return _gDetector.onTouchEvent(e);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			
		}
				 
	 }
}
